﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

using XMas;

namespace XMasContent
{
    public class FBXAnimParser
    {
        CSkeletonData skeleton_data;


        private class BoneAuxStruct
        {
            public float  FramePct;
            public int    BoneIdx;
            public int    BoneParent; 
            public Matrix Transform;
        };

        private class BoneMapClass
        {
            public int iBoneIdx;
            public int iBoneParentIdx;
            public string sBoneParentName;
        };


        public void ParseAllAnimations(CSkeletonData skeleton, IList<BoneContent> bones, AnimationContentDictionary animations)
        {
            // Build up a table mapping bone names to indices.
            skeleton_data = skeleton;
            Dictionary<string, BoneMapClass> boneMap = new Dictionary<string, BoneMapClass>();
            for (int i = 0; i < bones.Count; i++)
            {
                BoneMapClass boneData = new BoneMapClass();
                string boneName = bones[i].Name;

                if (!string.IsNullOrEmpty(boneName))
                {
                    boneData.iBoneIdx = i;
                    boneData.iBoneParentIdx = -1;
                    boneData.sBoneParentName = "";
                    if (bones[i].Parent != null && !string.IsNullOrEmpty(bones[i].Parent.Name))
                        boneData.sBoneParentName = bones[i].Parent.Name;
                    boneMap.Add(boneName, boneData);
                }
            }

            // Process parents
            Dictionary<string, BoneMapClass> boneMapAux = new Dictionary<string, BoneMapClass>(boneMap);
            foreach (KeyValuePair<string, BoneMapClass> boneDataAux in boneMap)            
            {
                if (!string.IsNullOrEmpty(boneDataAux.Value.sBoneParentName))
                {
                    BoneMapClass ResultBoneData;
                    if (boneMapAux.TryGetValue(boneDataAux.Value.sBoneParentName, out ResultBoneData))
                        boneDataAux.Value.iBoneParentIdx = ResultBoneData.iBoneIdx; 
                }
            }



            // Each animation
            foreach (KeyValuePair<string, AnimationContent> animation in animations)
            {
                //create animation entry
                CSkeletonData.CAnimationData current_animation = ProcessAnimation(animation.Value, boneMap);
                skeleton_data.addAnimation(animation.Key, current_animation);
            }
            if (skeleton_data.list_animations.Count == 0)
            {
                throw new InvalidContentException(
                            "Input file does not contain any animations.");
            }            
        }


        /// <summary>
        /// Converts an intermediate format content pipeline AnimationContent
        /// object to our runtime AnimationClip format.
        /// </summary>
        private CSkeletonData.CAnimationData ProcessAnimation(AnimationContent animation, Dictionary<string, BoneMapClass> boneMap)
        {

            List<BoneAuxStruct> boneAuxList = new List<BoneAuxStruct>();
            int iMaxFrames = 0;

            // For each input animation channel.
            foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels)
            {
                // Look up what bone this channel is controlling.
                BoneMapClass boneData;

                if (!boneMap.TryGetValue(channel.Key, out boneData))
                {
                    throw new InvalidContentException(string.Format(
                        "Found animation for bone '{0}', " +
                        "which is not part of the skeleton.", channel.Key));
                }

                // Convert the keyframe data.
                if (channel.Value.Count > iMaxFrames)
                    iMaxFrames = channel.Value.Count;

                foreach (AnimationKeyframe keyframe in channel.Value)
                {
                    // Calculate frame idx
                    float FramePct = (float)(keyframe.Time.TotalSeconds / animation.Duration.TotalSeconds);

                    BoneAuxStruct boneAux = new BoneAuxStruct();
                    boneAux.BoneIdx = boneData.iBoneIdx;
                    boneAux.FramePct = FramePct;
                    boneAux.BoneParent = boneData.iBoneParentIdx;
                    boneAux.Transform = keyframe.Transform;
                    boneAuxList.Add(boneAux);
                }
            }

            // Sort list by frame and bone
            boneAuxList.Sort(CompareBoneAux);



            // Set correctly
            CSkeletonData.CAnimationData anim = new CSkeletonData.CAnimationData();
            int iLastFrame = -1;
            int iExpectedBone = 0;
            CSkeletonData.CFramesData frame = null;
            foreach (BoneAuxStruct boneAux in boneAuxList)
            {
                int iCurrentFrame = (int)(boneAux.FramePct * iMaxFrames);
                if (iCurrentFrame != iLastFrame)
                {
                    // TODO: Check si nos pasamos de frames, deben seguir un orden
                    // Last frame
                    if (iLastFrame != -1)
                        anim.list_frames.Add(frame);
                    frame = new CSkeletonData.CFramesData();
                    iExpectedBone = 0;
                    iLastFrame = iCurrentFrame;
                }
                // Add bone
                Vector3 vScale = Vector3.One;
                Quaternion qRotation = Quaternion.Identity;
                Vector3 vTranslation = Vector3.Zero;

                boneAux.Transform.Decompose(out vScale, out qRotation, out vTranslation);
                qRotation.Normalize();
                                

                CSkeletonData.CSkeletonNode boneData = new CSkeletonData.CSkeletonNode();
                bool bDuplicate = false;
                
                // Check bone order                
                if (iExpectedBone != boneAux.BoneIdx)
                {
                    // Duplicate bone
                    if (iExpectedBone > boneAux.BoneIdx)
                        bDuplicate = true;
                    else
                    {
                        // Missing bones, add all necesary
                        for (int iBone = iExpectedBone; iBone < boneAux.BoneIdx; ++iBone)
                        {
                            if (iLastFrame > 0)
                            {
                                CSkeletonData.CSkeletonNode newBoneData = new CSkeletonData.CSkeletonNode();
                                newBoneData.oriented = anim.list_frames[iLastFrame - 1].list_skeleton[iBone].oriented;
                                newBoneData.parent = anim.list_frames[iLastFrame - 1].list_skeleton[iBone].parent;
                                newBoneData.pos = anim.list_frames[iLastFrame - 1].list_skeleton[iBone].pos;
                                frame.list_skeleton.Add(newBoneData);
                            }
                            else
                            {
                                CSkeletonData.CSkeletonNode newBoneData = new CSkeletonData.CSkeletonNode();
                                newBoneData.oriented = Quaternion.Identity;
                                newBoneData.parent = 0;
                                newBoneData.pos = Vector3.Zero;
                                frame.list_skeleton.Add(newBoneData);
                            }
                        }
                    }
                }
              
                if (!bDuplicate)
                {
                    boneData.parent = boneAux.BoneParent;
                    boneData.oriented = qRotation;
                    boneData.pos = vTranslation;
                    iExpectedBone = boneAux.BoneIdx + 1;
                    frame.list_skeleton.Add(boneData);
                }
            }

            // Add last one
            if (frame != null)
                anim.list_frames.Add(frame);

            anim.frame_rate = (float)(animation.Duration.TotalSeconds / iMaxFrames);
            return anim;
        }


        /// <summary>
        /// Comparison function for sorting keyframes into ascending time order.
        /// </summary>
        static int CompareBoneAux(BoneAuxStruct a, BoneAuxStruct b)
        {
            if (a.FramePct == b.FramePct)
            {
                if (a.BoneIdx == b.BoneIdx)
                    return 0;
                else
                {
                    if (a.BoneIdx > b.BoneIdx)
                        return 1;
                    else
                        return -1;
                }
            }
            else
            {
                if (a.FramePct > b.FramePct)
                    return 1;
                else
                    return -1;
            }
        }


    }
}